﻿/*=========================== SOFTWARE LICENSE AGREEMENT ===========================
 
    Copyright (c) 2012, Rob Lammert Software
 
    This file is part of SharpLog.
    Author:  Rob Lammert <rob@roblammert.com>

    Foobar is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
  =================================================================================*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace com.roblammert.sharplog
{
    /// <summary>
    /// Logging class for various logging duties within an application
    /// </summary>
    public class Logger
    {
        #region Data
        private static object mLock = new object();
        private static Logger mLogger = null;

        /// <summary>
        /// Gets the Logger instance.
        /// </summary>
        /// <value>
        /// The instance.
        /// </value>
        public static Logger Instance
        {
            get
            {
                // If this is the first time we’re referring to the
                // singleton object, the private variable will be null.
                if (mLogger == null)
                {
                    // for thread safety, lock an object when
                    // instantiating the new Logger object. This prevents
                    // other threads from performing the same block at the
                    // same time.
                    lock (mLock)
                    {
                        // Two or more threads might have found a null
                        // mLogger and are therefore trying to create a 
                        // new one. One thread will get to lock first, and
                        // the other one will wait until mLock is released.
                        // Once the second thread can get through, mLogger
                        // will have already been instantiated by the first
                        // thread so test the variable again. 
                        if (mLogger == null)
                        {
                            mLogger = new Logger();
                        }
                    }
                }
                return mLogger;
            }
        }

        private List<ILogger> mObservers;

        #endregion

        #region Constructor
        private Logger()
        {
            mObservers = new List<ILogger>();
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Registers the observer.
        /// </summary>
        /// <param name='observer'>
        /// Observer.
        /// </param>
        public void RegisterObserver(ILogger observer)
        {
            if (!mObservers.Contains(observer))
            {
                mObservers.Add(observer);
            }
        }

        /// <summary>
        /// Unregisters the observer.
        /// </summary>
        /// <param name='observer'>
        /// Observer.
        /// </param>
        public void UnregisterObserver(ILogger observer)
        {
            if (mObservers.Contains(observer))
            {
                mObservers.Remove(observer);
            }
        }
        
        /// <summary>
        /// Send an event to all of the registered observers
        /// </summary>
        /// <param name="level">Severity of the event</param>
        /// <param name="facility">Facility of the event</param>
        /// <param name="message">Message associated with the event</param>
        public void AddLogMessage(Severity level, Facility facility, string message)
        {
            StackFrame stackFrame = new StackFrame(1, true);

            String raw = stackFrame.GetMethod().ToString();
            String method = stackFrame.GetMethod().DeclaringType.FullName.ToString();
            String clean = "";
            if (raw.Contains(".ctor"))
            {
                method =
                clean = raw.Replace(".ctor", method);
            }
            else
            {
                clean = raw.Insert(raw.IndexOf(" ") + 1, method + ".");
            }

            foreach (ILogger observer in mObservers)
            {
                observer.ProcessLogMessage(DateTime.Now, level, facility, clean, message);
            }
        }

        /// <summary>
        /// Send a stacktrace of an event to all of the registered observers
        /// </summary>
        /// <param name="level">Severity of the event</param>
        /// <param name="facility">Facility of the event</param>
        /// <param name="ex">Exception thrown by the event</param>
        public void AddLogMessage(Severity level, Facility facility, Exception ex)
        {
            StackFrame stackFrame = new StackFrame(1, true);

            string method = stackFrame.GetMethod().ToString();
            int line = stackFrame.GetFileLineNumber();

            StringBuilder message = new StringBuilder(ex.Message);
            string trace = ex.StackTrace;

            while (ex.InnerException != null)
            {
                ex = ex.InnerException;
                message.Append("\n" + ex.Message);
            }
            message.Append("\nStack trace: " + trace);

            foreach (ILogger observer in mObservers)
            {
                observer.ProcessLogMessage(DateTime.Now, level, facility, method, message.ToString());
            }
        }
        #endregion

        /// <summary>
        /// Severity of the log that the message is either assigned to or to be displayed under.
        /// </summary>
        public enum Severity
        {
            /// <summary>
            /// Debug-level messages.
            /// </summary>
            Debug = 7,
            /// <summary>
            /// Informational messages.
            /// </summary>
            Invormational = 6,
            /// <summary>
            /// Normal but significant condition.
            /// </summary>
            Notice = 5,
            /// <summary>
            /// Warning conditions.
            /// </summary>
            Warning = 4,
            /// <summary>
            /// Error conditions.
            /// </summary>
            Error = 3,
            /// <summary>
            /// Critical conditions.
            /// </summary>
            Critical = 2,
            /// <summary>
            /// Action must be taken immediately.
            /// </summary>
            Alert = 1,
            /// <summary>
            /// System is unusable.
            /// </summary>
            Emergency = 0
        }

        /// <summary>
        /// The Facility value is a way of determining which process of the machine created the message. 
        /// Since the Syslog protocol was originally written on BSD Unix, the Facilities reflect the names 
        /// of Unix processes and Daemons.
        /// The priority value is calculated using the following formula:
        /// Priority = Facility * 8 + Level
        /// </summary>
        public enum Facility
        {
            kernel = 0, 

            User = 1, 

            MailSystem = 2,

            SystemDaemon = 3, 

            Security = 4,
 
            Syslogd = 5, 

            LinePrinterSubsystem = 6, 

            NetworkNewsSubsystem = 7, 

            UUCPSubsystem = 8,

            ClockDaemon = 9,
 
            SecurityMessages = 10,
 
            FTPDaemon = 11, 

            NTPSubsystem = 12, 

            LogAudit = 13,
 
            LogAlert = 14,
 
            ClockDaemon2 = 15,
 
            Local0 = 16, 

            Local1 = 17,

            Local2 = 18, 

            Local3 = 19, 

            Local4 = 20, 

            Local5 = 21, 

            Local6 = 22, 

            Local7 = 23

        }
    }
}
